﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Threading;

namespace Dashboard.Models
{
    internal class DataManager : IDataManager
    {
        private struct Data
        {
            private object lockObjSeconds;
            private object lockObjMinutes;
            private object lockObjHours;
            private object lockObjDays;

            private List<byte> days;
            public List<byte> Days
            {
                get
                {
                    lock (lockObjDays)
                    {
                        return this.days;
                    }
                }
                private set
                {
                    lock (lockObjDays)
                    {
                        this.days = value;
                    }
                }
            }

            private List<byte> hours;
            public List<byte> Hours
            {
                get
                {
                    lock (lockObjHours)
                    {
                        return this.hours;
                    }
                }
                private set
                {
                    lock (lockObjHours)
                    {
                        this.hours = value;
                    }
                }
            }

            private List<byte> minutes;
            public List<byte> Minutes
            {
                get
                {
                    lock (lockObjMinutes)
                    {
                        return this.minutes;
                    }
                }
                private set
                {
                    lock (lockObjMinutes)
                    {
                        this.minutes = value;
                    }
                }
            }

            private List<byte> seconds;
            public List<byte> Seconds
            {
                get
                {
                    lock (lockObjSeconds)
                    {
                        return this.seconds;
                    }
                }
                private set
                {
                    lock (lockObjSeconds)
                    {
                        this.seconds = value;
                    }
                }
            }

            public Data(List<byte> days, List<byte> hours, List<byte> minutes, List<byte> seconds)
            {
                this.days = days;
                this.hours = hours;
                this.minutes = minutes;
                this.seconds = seconds;

                this.lockObjDays = new object();
                this.lockObjHours = new object();
                this.lockObjMinutes = new object();
                this.lockObjSeconds = new object();
            }
        }

        //Two up level are needed selection
        #region IDataManager
        public List<byte> GetDays(Counter counter)
        {
            return new List<byte>(dataCounters[(int)counter].Days);
        }

        public List<byte> GetHours(Counter counter)
        {
            return new List<byte>(dataCounters[(int)counter].Hours);
        }

        public List<byte> GetMinutes(Counter counter)
        {
            return new List<byte>(dataCounters[(int)counter].Minutes);
        }

        public List<byte> GetSeconds(Counter counter)
        {
            return new List<byte>(dataCounters[(int)counter].Seconds);
        }


        public List<string> GetNamesAllCounter()
        {
            return new List<string>(this.namesCounters);
        }

        Func<List<byte>, List<byte>> SMA = delegate(List<byte> list)
        {
            List<byte> result = new List<byte>();
            int sum = 0;
            int count = 0;
            int half = 2;
            while (count < list.Count)
            {
                int temp = 0;
                for (int i = 0; i < half; i++)
                {
                    temp += list[count + i];
                }

                sum += temp / half;
                count += half;

                result.Add((byte)(sum / (count / half)));
            }
            return result;
        };

        Func<List<byte>, List<byte>> EMA = delegate(List<byte> list)
        {
            List<byte> result = new List<byte>();

            int half = 1;
            int count = 1;

            Func<byte> GetMean = delegate()
            {
                int temp = 0;
                for (int i = 0; i < half; i++)
                {
                    temp += list[count + i];
                }

                temp /= half;
                return (byte)temp;
            };

            double a = 2 / (double)(list.Count / half + 1);
            result.Add(GetMean());
            while (count < list.Count)
            {
                int temp = GetMean();

                result.Add((byte)(a * temp + (1 - a) * result[count / half - 1]));
                count += half;
            }
            return result;
        };

        Func<List<byte>, int, List<byte>> SelectLast = delegate(List<byte> list, int count)
        {
            List<byte> result = new List<byte>();
            for (int i = list.Count - count; i < list.Count; i++)
            {
                result.Add(list[i]);
            }
            return result;
        };

        public List<List<byte>> GetDaysAllCounter()
        {
            List<List<byte>> result = new List<List<byte>>();
            for (int i = 0; i < readers.Length; i++)
            {
                List<byte> temp = SelectLast(dataCounters[i].Days, (int)Count.Days * (int)Section.Day);
                result.Add(EMA(temp));
            }

            return result;
        }

        public List<List<byte>> GetHoursAllCounter()
        {
            List<List<byte>> result = new List<List<byte>>();
            for (int i = 0; i < readers.Length; i++)
            {
                List<byte> temp = SelectLast(dataCounters[i].Hours, (int)Count.Hours * (int)Section.Hour);
                result.Add(EMA(temp));
            }

            return result;
        }

        public List<List<byte>> GetMinutesAllCounter()
        {
            List<List<byte>> result = new List<List<byte>>();
            for (int i = 0; i < readers.Length; i++)
            {
                List<byte> temp = SelectLast(dataCounters[i].Minutes, (int)Count.Minutes * (int)Section.Minute);
                result.Add(EMA(temp));
            }

            return result;
        }

        public List<List<byte>> GetSecondsAllCounter()
        {
            List<List<byte>> result = new List<List<byte>>();
            for (int i = 0; i < readers.Length; i++)
            {
                List<byte> temp = SelectLast(dataCounters[i].Seconds, (int)Count.Seconds * (int)Section.Second);
                result.Add(EMA(temp));
            }

            return result;
        }


        public List<byte> SelectDays(int from, int before)
        {
            throw new NotImplementedException();
        }

        public List<byte> SelectHours(int from, int before)
        {
            throw new NotImplementedException();
        }

        public List<byte> SelectMinutes(int from, int before)
        {
            throw new NotImplementedException();
        }

        public List<byte> SelectSeconds(int from, int before)
        {
            throw new NotImplementedException();
        }
        #endregion

        private int curCountSeconds = 0;
        private int curCountMinutes = 0;
        private int curCountHours = 0;

        private Random generator;

        private static Thread thread;

        private IReaderData[] readers;
        private List<Data> dataCounters;
        private List<string> namesCounters;

        private Dictionary<Type, string> names = new Dictionary<Type, string>() { { typeof(ReadDataRAM), "RAM" },   
                                                                                  { typeof(ReadDataCPU), "CPU" }, 
                                                                                  { typeof(ReadDataHDD), "HDD" } };

        private static IDataManager dataManager = new DataManager();
        internal static IDataManager Instance
        {
            get { return dataManager; }
        }

        private DataManager(params IReaderData[] readers) :
            this()
        {
            this.readers = readers;

            for (int i = 0; i < readers.Length; i++)
            {
                namesCounters.Add(names[readers[i].GetType()]);

                Data data = this.GenerateFakeData();
                dataCounters.Add(data);
            }
        }

        private DataManager()
        {
            this.dataCounters = new List<Data>();
            this.generator = new Random();
            this.namesCounters = new List<string>();
            thread = new Thread(ReadData);
        }

        private Data GenerateFakeData()
        {
            #region HelpMethods
            Func<List<byte>, byte> GetMean = delegate(List<byte> list)
            {
                Func<byte, int> function = delegate(byte value) { return (int)value; };
                return (byte)(list.Sum(function) / list.Count);
            };

            Action<List<byte>, List<byte>> AddMean = delegate(List<byte> first, List<byte> second)
            {
                first.Add(GetMean(second));
                second.Clear();
            };

            Action<List<byte>, List<byte>, int, int> Update = delegate(List<byte> first, List<byte> second, int section, int count)
            {
                for (int i = 0; i < section; i++)
                {
                    List<byte> temp = new List<byte>();
                    for (int j = 0; j < count; j++)
                    {
                        temp.Add(second[j]);
                    }
                    AddMean(first, temp);
                }
            };

            Action<List<byte>, int, int> Add = delegate(List<byte> list, int section, int count)
            {
                for (int i = 0; i < count * section - section; i++)
                {
                    list.Insert(0, (byte)generator.Next((int)Statistic.MinValue, (int)Statistic.MaxValue));
                }
            };
            #endregion

            List<byte> days = new List<byte>();
            List<byte> hours = new List<byte>();
            List<byte> minutes = new List<byte>();
            List<byte> seconds = new List<byte>();

            for (int i = 0; i < (int)Count.Seconds * (int)Section.Second; i++)
            {
                seconds.Add((byte)generator.Next((int)Statistic.MinValue, (int)Statistic.MaxValue));
            }

            Update(minutes, seconds, (int)Section.Minute, (int)Count.Minutes);
            Add(minutes, (int)Section.Minute, (int)Count.Minutes);

            Update(hours, minutes, (int)Section.Hour, (int)Count.Hours);
            Add(hours, (int)Section.Hour, (int)Count.Hours);

            Update(days, hours, (int)Section.Day, (int)Count.Days);
            Add(days, (int)Section.Day, (int)Count.Days);

            return new Data(days, hours, minutes, seconds);
        }

        internal static void Run(params IReaderData[] readers)
        {
            dataManager = new DataManager(readers);
            thread.Start();
        }

        #region HelpMethods
        private Action<List<byte>> Except = delegate(List<byte> values)
         {
             for (int i = 0; i < values.Count; i++)
             {
                 if (values[i] > (int)Statistic.MaxValue || values[i] < (int)Statistic.MinValue)
                 {
                     throw new ArgumentOutOfRangeException();
                 }
             }
         };

        private Func<List<byte>, int, List<byte>> GetLastSection = delegate(List<byte> list, int countSection)
        {
            int length = list.Count;
            int start = length / countSection * (countSection - 1);

            List<byte> result = new List<byte>();
            for (int i = start; i < length; i++)
            {
                result.Add(list[i]);
            }

            return result;
        };

        private Func<List<byte>, byte> GetMean = delegate(List<byte> list)
        {
            Func<byte, int> function = delegate(byte value) { return (int)value; };
            return (byte)(list.Sum(function) / list.Count);
        };
        #endregion

        private void ReadData()
        {
            while (true)
            {
                List<byte> values = new List<byte>();
                for (int i = 0; i < dataCounters.Count; i++)
                {
                    values.Add(this.readers[i].GetData());
                }

                UpdateSeconds(values);
                Thread.Sleep((int)Count.Milliseconds / (int)Section.Second);
            }
        }

        private void UpdateSeconds(List<byte> values)
        {
            Except(values);

            for (int i = 0; i < dataCounters.Count; i++)
            {
                dataCounters[i].Seconds.RemoveAt(0);
                dataCounters[i].Seconds.Add(values[i]);
            }
            curCountSeconds++;

            if (curCountSeconds == (int)UpdateCount.Seconds)
            {
                curCountSeconds = 0;

                List<byte> result = new List<byte>();
                for (int i = 0; i < dataCounters.Count; i++)
                {
                    result.Add(GetMean(GetLastSection(dataCounters[i].Seconds, (int)Section.Second)));
                }
                UpdateMinutes(result);
            }
        }

        private void UpdateMinutes(List<byte> values)
        {
            Except(values);
            for (int i = 0; i < dataCounters.Count; i++)
            {
                dataCounters[i].Minutes.RemoveAt(0);
                dataCounters[i].Minutes.Add(values[i]);
            }
            curCountMinutes++;

            if (curCountMinutes == (int)UpdateCount.Minutes)
            {
                curCountMinutes = 0;

                List<byte> result = new List<byte>();
                for (int i = 0; i < dataCounters.Count; i++)
                {
                    result.Add(GetMean(GetLastSection(dataCounters[i].Minutes, (int)Section.Minute)));
                }
                UpdateHours(result);
            }
        }

        private void UpdateHours(List<byte> values)
        {
            Except(values);
            for (int i = 0; i < dataCounters.Count; i++)
            {
                dataCounters[i].Hours.Add(values[i]);
            }
            curCountHours++;

            if (curCountHours == (int)UpdateCount.Hours)
            {
                curCountHours = 0;
                List<byte> result = new List<byte>();
                for (int i = 0; i < dataCounters.Count; i++)
                {
                    result.Add(GetMean(GetLastSection(dataCounters[i].Hours, (int)Section.Hour)));
                }
                UpdateDays(result);
            }
        }

        private void UpdateDays(List<byte> values)
        {
            Except(values);
            for (int i = 0; i < dataCounters.Count; i++)
            {
                dataCounters[i].Hours.Add(values[i]);
            }
        }
    }
}